/* This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
#include <windows.h>
#include "Bitmap.h"

/* DibRowLength() returns the number of bytes in each row of pixels */
DWORD DibRowLength(BITMAPINFOHEADER *const pBitmapInfo)
{
	return 4 * ((pBitmapInfo->biWidth * pBitmapInfo->biBitCount + 31) / 32);
}

/* DibNumColours() returns the number of colours in the colour
   table */
int DibNumColours(BITMAPINFOHEADER *const pBitmapInfo)
{
	if (pBitmapInfo->biClrUsed != 0)
		return pBitmapInfo->biClrUsed;
	else if (pBitmapInfo->biBitCount <= 8)
		return 1 << pBitmapInfo->biBitCount;
	else
		return 0;
}

/* DibBitsSize() returns the size in bytes of the "bits" section of the
   device independent bitmap i.e. where the actual image is stored */
DWORD DibBitsSize(BITMAPINFOHEADER *const pBitmapInfo)
{
	if (pBitmapInfo->biSizeImage != 0)
		return pBitmapInfo->biSizeImage;
	else
		return pBitmapInfo->biHeight * DibRowLength(pBitmapInfo);
}

/* SaveDibFile() takes the two components of a device independent bitmap, the
   bitmap information and the "bits", and saves it to file, returning TRUE
   if successful and FALSE otherwise.  The structure of a DIB is:
   1) file header
   2) information header
   3) RGB colour table (if colour depth of 256 colours or less)
   4) the bitmap pixel bits */ 
   
BOOL SaveDibFile(const TCHAR *szFileName, BITMAPINFO *const pBitmapInfo, 
                 const void *pBits)
{
	BITMAPFILEHEADER bitmapHeader;
	BOOL bSuccess;
	DWORD dwHeaderSize, dwBytesWritten, dwBitSize;
	HANDLE hFile;

	/* make sure the DIB is valid */
	if (pBitmapInfo == NULL)
		return FALSE;

	/* create the file on disk */
	hFile = CreateFile(szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 
	                   FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;

	/* initialise the fields of the bitmap header, which is at the top of
	   a DIB file */
	dwHeaderSize = sizeof(BITMAPINFOHEADER);
	dwHeaderSize += sizeof(RGBQUAD) * DibNumColours(&pBitmapInfo->bmiHeader);
	bitmapHeader.bfType = *(WORD *) "BM";
	bitmapHeader.bfSize = dwHeaderSize + DibBitsSize(&pBitmapInfo->bmiHeader);
	bitmapHeader.bfReserved1 = 0;
	bitmapHeader.bfReserved2 = 0;
	bitmapHeader.bfOffBits = dwHeaderSize + sizeof(BITMAPFILEHEADER);

	/* write the file header to disk, checking for errors */
	bSuccess = WriteFile(hFile, &bitmapHeader, sizeof(BITMAPFILEHEADER),
	                     &dwBytesWritten, NULL);
	if (!bSuccess || (dwBytesWritten != sizeof(BITMAPFILEHEADER)))
	{
		CloseHandle(hFile);
		DeleteFile(szFileName);
		return FALSE;
	}

	/* for packed DIBs, determine where the "bits" of the DIB start */
	if (pBitmapInfo == NULL) 
		pBits = (BYTE *) pBitmapInfo + dwHeaderSize;
	/* write the information header to the file, checking for errors */
	bSuccess = WriteFile(hFile, pBitmapInfo, dwHeaderSize, &dwBytesWritten, 
	                     NULL);
	if (!bSuccess || dwBytesWritten != dwHeaderSize)
	{
		CloseHandle(hFile);
		DeleteFile(szFileName);
		return FALSE;
	}

	/* write the "bits" part of the DIB to the file, checking for errors */
	dwBitSize = DibBitsSize(&pBitmapInfo->bmiHeader);
	bSuccess = WriteFile(hFile, pBits, dwBitSize, &dwBytesWritten, NULL);
	/* close the file */
	CloseHandle(hFile);
	if (!bSuccess || dwBytesWritten != dwBitSize)
	{
		DeleteFile(szFileName);
		return FALSE;
	}
	else /* if successful */
		return TRUE;
}

/* CopyBitmap() takes a bitmap handle and a device context handle, and
   copies to a new bitmap which is returned as a handle.  This is done
   by bltting from hdcSource to hdcDest. */
HBITMAP CopyBitmap(HBITMAP hSource)
{
	HDC hdcDest, hdcSource;
	BITMAP bitmap;	
	HBITMAP hDest;

	hdcSource = CreateCompatibleDC(NULL);
	hdcDest = CreateCompatibleDC(NULL);
	SelectObject(hdcSource, hSource);

	GetObject(hSource, sizeof(BITMAP), &bitmap);
	hDest = CreateBitmapIndirect(&bitmap);
	SelectObject(hdcDest, hDest);
	BitBlt(hdcDest, 0, 0, bitmap.bmWidth, bitmap.bmHeight, 
	       hdcSource, 0, 0, SRCCOPY);

	DeleteDC(hdcSource);
	DeleteDC(hdcDest);
	return hDest;
}

/* SaveBitmap() takes a normal bitmap, which will be device dependent (a DDB)
   and creates a device independent bitmap section of the same dimensions.  It
   then copies the DDB to the DIB section and saves the DIB section to the
   given filename */
BOOL SaveBitmap(const TCHAR *szFileName, HBITMAP hSource, 
                int xImageRes, int yImageRes)
{
	BITMAPINFO bitmapInfo;
	BYTE *pBits;
	HBITMAP hDest;
	HDC hdc, hdcDest, hdcSource;
	BOOL bSuccess;

	hdc = GetDC(NULL);
	hdcDest = CreateCompatibleDC(hdc);
	hdcSource = CreateCompatibleDC(hdc);
	/* initialise the DIB section header */
	bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bitmapInfo.bmiHeader.biBitCount = GetDeviceCaps(hdc, BITSPIXEL);
	bitmapInfo.bmiHeader.biClrImportant = 0;
	bitmapInfo.bmiHeader.biClrUsed = 0;
	bitmapInfo.bmiHeader.biCompression = BI_RGB;
	bitmapInfo.bmiHeader.biHeight = yImageRes;
	bitmapInfo.bmiHeader.biWidth = xImageRes;
	bitmapInfo.bmiHeader.biPlanes = 1;
	bitmapInfo.bmiHeader.biSizeImage = 0;
	bitmapInfo.bmiHeader.biXPelsPerMeter = 0;
	bitmapInfo.bmiHeader.biYPelsPerMeter = 0;

	hDest = CreateDIBSection(NULL, &bitmapInfo, 0, (void **) &pBits, NULL, 0);
	SelectObject(hdcDest, hDest);
	SelectObject(hdcSource, hSource);
	BitBlt(hdcDest, 0, 0, xImageRes, yImageRes, hdcSource, 0, 0, SRCCOPY);
	bSuccess = SaveDibFile(szFileName, &bitmapInfo, pBits);

	DeleteDC(hdcDest);
	DeleteDC(hdcSource);
	DeleteObject(hDest);
	ReleaseDC(NULL, hdc);
	return bSuccess;
}

void InitialiseBitmap(COLORREF initialColour, HBITMAP hBitmap, 
                      const int xWidth, const int yHeight)
{
	HDC hdcBitmap;
	RECT rect;
	HBRUSH hBrush;

	rect.top = rect.left = 0;
	rect.bottom = yHeight;
	rect.right = xWidth;

	hdcBitmap = CreateCompatibleDC(NULL);
	hBrush = CreateSolidBrush(initialColour);
	SelectObject(hdcBitmap, hBitmap);
	FillRect(hdcBitmap, &rect, hBrush);

	DeleteDC(hdcBitmap);
	DeleteObject(hBrush);
}